﻿using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.Drawing;
using System.IO;
using System.Windows.Forms;
using GMap.NET;
using GMap.NET.WindowsForms;
using System.Drawing.Drawing2D;
using System.Drawing.Imaging;

namespace Demo.WindowsForms
{
   public partial class StaticImage : Form
   {
      MainForm Main;

      BackgroundWorker bg = new BackgroundWorker();
      readonly List<GMap.NET.Point> tileArea = new List<GMap.NET.Point>();

      public StaticImage(MainForm main)
      {
         InitializeComponent();

         Main = main;

         numericUpDown1.Maximum = Main.MainMap.MaxZoom;
         numericUpDown1.Minimum = Main.MainMap.MinZoom;
         numericUpDown1.Value = new decimal(Main.MainMap.Zoom);

         bg.WorkerReportsProgress = true;
         bg.WorkerSupportsCancellation = true;
         bg.DoWork += new DoWorkEventHandler(bg_DoWork);
         bg.ProgressChanged += new ProgressChangedEventHandler(bg_ProgressChanged);
         bg.RunWorkerCompleted += new RunWorkerCompletedEventHandler(bg_RunWorkerCompleted);
      }

      void bg_RunWorkerCompleted(object sender, RunWorkerCompletedEventArgs e)
      {
         if(!e.Cancelled)
         {
            if(e.Error != null)
            {
               MessageBox.Show("Error:" + e.Error.ToString(), "GMap.NET", MessageBoxButtons.OK, MessageBoxIcon.Error);
            }
            else if(e.Result != null)
            {
               try
               {
                  Process.Start(e.Result as string);
               }
               catch
               {
               }
            }
         }

         this.Text = "Static Map maker";
         progressBar1.Value = 0;
         button1.Enabled = true;
         numericUpDown1.Enabled = true;
      }

      void bg_ProgressChanged(object sender, ProgressChangedEventArgs e)
      {
         progressBar1.Value = e.ProgressPercentage;

         GMap.NET.Point p = (GMap.NET.Point) e.UserState;
         this.Text = "Static Map maker: Downloading[" + p + "]: " + tileArea.IndexOf(p) + " of " + tileArea.Count;
      }

      void bg_DoWork(object sender, DoWorkEventArgs e)
      {
         MapInfo info = (MapInfo) e.Argument;
         if(!info.Area.IsEmpty)
         {
            var types = GMaps.Instance.GetAllLayersOfType(info.Type);

            string bigImage = Environment.GetFolderPath(Environment.SpecialFolder.DesktopDirectory) + Path.DirectorySeparatorChar + "GMap at zoom " + info.Zoom + " - " + types[0] + "-" + DateTime.Now.Ticks + ".png";
            e.Result = bigImage;

            // current area
            GMap.NET.Point topLeftPx = info.Projection.FromLatLngToPixel(info.Area.LocationTopLeft, info.Zoom);
            GMap.NET.Point rightButtomPx = info.Projection.FromLatLngToPixel(info.Area.Bottom, info.Area.Right, info.Zoom);
            GMap.NET.Point pxDelta = new GMap.NET.Point(rightButtomPx.X - topLeftPx.X, rightButtomPx.Y - topLeftPx.Y);

            int padding = info.MakeWorldFile ? 0 : 22;
            {
               using(Bitmap bmpDestination = new Bitmap(pxDelta.X + padding*2, pxDelta.Y + padding*2))
               {
                  using(Graphics gfx = Graphics.FromImage(bmpDestination))
                  {
                     gfx.InterpolationMode = InterpolationMode.HighQualityBicubic;
                     gfx.SmoothingMode = SmoothingMode.HighQuality;

                     int i = 0;

                     // get tiles & combine into one
                     lock(tileArea)
                     {
                        foreach(var p in tileArea)
                        {
                           if(bg.CancellationPending)
                           {
                              e.Cancel = true;
                              return;
                           }

                           int pc = (int) (((double) ++i / tileArea.Count)*100);
                           bg.ReportProgress(pc, p);

                           foreach(MapType tp in types)
                           {
                              Exception ex;
                              WindowsFormsImage tile = GMaps.Instance.GetImageFrom(tp, p, info.Zoom, out ex) as WindowsFormsImage;
                              if(tile != null)
                              {
                                 using(tile)
                                 {
                                    int x = p.X*info.Projection.TileSize.Width - topLeftPx.X + padding;
                                    int y = p.Y*info.Projection.TileSize.Width - topLeftPx.Y + padding;
                                    {
                                       gfx.DrawImage(tile.Img, x, y, info.Projection.TileSize.Width, info.Projection.TileSize.Height);
                                    }
                                 }
                              }
                           }
                        }
                     }

                     // draw routes
                     {
                        foreach(GMapRoute r in Main.routes.Routes)
                        {
                           using(GraphicsPath rp = new GraphicsPath())
                           {
                              for(int j = 0; j < r.Points.Count; j++)
                              {
                                 var pr = r.Points[j];
                                 GMap.NET.Point px = info.Projection.FromLatLngToPixel(pr.Lat, pr.Lng, info.Zoom);

                                 px.Offset(padding, padding);
                                 px.Offset(-topLeftPx.X, -topLeftPx.Y);

                                 GMap.NET.Point p2 = px;

                                 if(j == 0)
                                 {
                                    rp.AddLine(p2.X, p2.Y, p2.X, p2.Y);
                                 }
                                 else
                                 {
                                    System.Drawing.PointF p = rp.GetLastPoint();
                                    rp.AddLine(p.X, p.Y, p2.X, p2.Y);
                                 }
                              }

                              if(rp.PointCount > 0)
                              {
                                 gfx.DrawPath(r.Stroke, rp);
                              }
                           }
                        }
                     }

                     // draw polygons
                     {
                        foreach(GMapPolygon r in Main.polygons.Polygons)
                        {
                           using(GraphicsPath rp = new GraphicsPath())
                           {
                              for(int j = 0; j < r.Points.Count; j++)
                              {
                                 var pr = r.Points[j];
                                 GMap.NET.Point px = info.Projection.FromLatLngToPixel(pr.Lat, pr.Lng, info.Zoom);

                                 px.Offset(padding, padding);
                                 px.Offset(-topLeftPx.X, -topLeftPx.Y);

                                 GMap.NET.Point p2 = px;

                                 if(j == 0)
                                 {
                                    rp.AddLine(p2.X, p2.Y, p2.X, p2.Y);
                                 }
                                 else
                                 {
                                    System.Drawing.PointF p = rp.GetLastPoint();
                                    rp.AddLine(p.X, p.Y, p2.X, p2.Y);
                                 }
                              }

                              if(rp.PointCount > 0)
                              {
                                 rp.CloseFigure();

                                 gfx.FillPath(r.Fill, rp);

                                 gfx.DrawPath(r.Stroke, rp);
                              }
                           }
                        }
                     }

                     // draw markers
                     {
                        foreach(GMapMarker r in Main.objects.Markers)
                        {
                           var pr = r.Position;
                           GMap.NET.Point px = info.Projection.FromLatLngToPixel(pr.Lat, pr.Lng, info.Zoom);

                           px.Offset(padding, padding);
                           px.Offset(-topLeftPx.X, -topLeftPx.Y);
                           px.Offset(r.Offset.X, r.Offset.Y);

                           r.LocalPosition = new System.Drawing.Point(px.X, px.Y);

                           r.OnRender(gfx);
                        }

                        // tooltips above
                        foreach(GMapMarker m in Main.objects.Markers)
                        {
                           if(m.ToolTip != null && m.Visible)
                           {
                              if(!string.IsNullOrEmpty(m.ToolTipText))
                              {
                                 m.ToolTip.Draw(gfx);
                              }
                           }
                        }
                     }

                     // draw info
                     {
                        System.Drawing.Rectangle rect = new System.Drawing.Rectangle();
                        {
                           rect.Location = new System.Drawing.Point(padding, padding);
                           rect.Size = new System.Drawing.Size(pxDelta.X, pxDelta.Y);
                        }

                        using(Font f = new Font(FontFamily.GenericSansSerif, 9, FontStyle.Bold))
                        {
                           // draw bounds & coordinates
                           using(Pen p = new Pen(Brushes.DimGray, 3))
                           {
                              p.DashStyle = System.Drawing.Drawing2D.DashStyle.DashDot;

                              gfx.DrawRectangle(p, rect);

                              string topleft = info.Area.LocationTopLeft.ToString();
                              SizeF s = gfx.MeasureString(topleft, f);

                              gfx.DrawString(topleft, f, p.Brush, rect.X + s.Height/2, rect.Y + s.Height/2);

                              string rightBottom = new PointLatLng(info.Area.Bottom, info.Area.Right).ToString();
                              SizeF s2 = gfx.MeasureString(rightBottom, f);

                              gfx.DrawString(rightBottom, f, p.Brush, rect.Right - s2.Width - s2.Height/2, rect.Bottom - s2.Height - s2.Height/2);
                           }

                           // draw scale
                           using(Pen p = new Pen(Brushes.Blue, 1))
                           {
                              double rez = info.Projection.GetGroundResolution(info.Zoom, info.Area.Bottom);
                              int px100 = (int) (100.0 / rez); // 100 meters
                              int px1000 = (int) (1000.0 / rez); // 1km   

                              gfx.DrawRectangle(p, rect.X + 10, rect.Bottom - 20, px1000, 10);
                              gfx.DrawRectangle(p, rect.X + 10, rect.Bottom - 20, px100, 10);

                              string leftBottom = "scale: 100m | 1Km";
                              SizeF s = gfx.MeasureString(leftBottom, f);
                              gfx.DrawString(leftBottom, f, p.Brush, rect.X+10, rect.Bottom - s.Height - 20);
                           }
                        }
                     }
                  }

                  bmpDestination.Save(bigImage, ImageFormat.Png);
               }
            }

            //The worldfile for the original image is:

            //0.000067897543      // the horizontal size of a pixel in coordinate units (longitude degrees in this case);
            //0.0000000
            //0.0000000
            //-0.0000554613012    // the comparable vertical pixel size in latitude degrees, negative because latitude decreases as you go from top to bottom in the image.
            //-111.743323868834   // longitude of the pixel in the upper-left-hand corner.
            //35.1254392635083    // latitude of the pixel in the upper-left-hand corner.

            // generate world file
            if(info.MakeWorldFile)
            {
               string wf = bigImage + "w";
               using(StreamWriter world = File.CreateText(wf))
               {
                  world.WriteLine("{0:0.000000000000}", (info.Area.WidthLng / pxDelta.X));
                  world.WriteLine("0.0000000");
                  world.WriteLine("0.0000000");
                  world.WriteLine("{0:0.000000000000}", (-info.Area.HeightLat / pxDelta.Y));
                  world.WriteLine("{0:0.000000000000}", info.Area.Left);
                  world.WriteLine("{0:0.000000000000}", info.Area.Top);
                  world.Close();
               }
            }
         }
      }

      readonly List<PointLatLng> GpxRoute = new List<PointLatLng>();
      RectLatLng AreaGpx = RectLatLng.Empty;

      private void button1_Click(object sender, EventArgs e)
      {
         RectLatLng? area = null;

         if(checkBoxRoutes.Checked)
         {
            area = Main.MainMap.GetRectOfAllRoutes(null);
            if(!area.HasValue)
            {
               MessageBox.Show("No routes in map", "GMap.NET", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
               return;
            }
         }
         else
         {
            area = Main.MainMap.SelectedArea;
            if(area.Value.IsEmpty)
            {
               MessageBox.Show("Select map area holding ALT", "GMap.NET", MessageBoxButtons.OK, MessageBoxIcon.Exclamation);
               return;
            }
         }

         if(!bg.IsBusy)
         {
            lock(tileArea)
            {
               tileArea.Clear();
               tileArea.AddRange(Main.MainMap.Projection.GetAreaTileList(area.Value, (int) numericUpDown1.Value, 1));
               tileArea.TrimExcess();
            }

            numericUpDown1.Enabled = false;
            progressBar1.Value = 0;
            button1.Enabled = false;

            bg.RunWorkerAsync(new MapInfo(Main.MainMap.Projection, area.Value, (int) numericUpDown1.Value, Main.MainMap.MapType, checkBoxWorldFile.Checked));
         }
      }

      private void button2_Click(object sender, EventArgs e)
      {
         if(bg.IsBusy)
         {
            bg.CancelAsync();
         }
      }
   }

   public struct MapInfo
   {
      public PureProjection Projection;
      public RectLatLng Area;
      public int Zoom;
      public MapType Type;
      public bool MakeWorldFile;

      public MapInfo(PureProjection Projection, RectLatLng Area, int Zoom, MapType Type, bool makeWorldFile)
      {
         this.Projection = Projection;
         this.Area = Area;
         this.Zoom = Zoom;
         this.Type = Type;
         this.MakeWorldFile = makeWorldFile;
      }
   }
}
